package com.share.tools;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @desc: 防刷工具类
 * @author:caifan
 * @date:2020/5/16
 */
public class RedisLimitUtils {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static RedisLimitUtils redisUtils;

    @PostConstruct
    public void init() {
        redisUtils = this;
        redisUtils.redisTemplate = this.redisTemplate;
        redisUtils.stringRedisTemplate = this.stringRedisTemplate;

    }

    /**
     * 设置分布式锁
     *
     * @param key     键
     * @param value   值
     * @param timeOut 有效期，单位：ms
     * @return
     */
    public static boolean lock(String key, String value, long timeOut) {
        //采用NX方式（当锁定资源不存在的时候才能Set成功）设置锁
        Boolean lockResult = redisUtils.stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.MILLISECONDS);
        if (lockResult != null && lockResult) {
            return true;
        }
        //若Set失败, 则判断锁是否超时
        //判断锁是否超时, 超时则重新设置, 防止异常操作不释放锁时导致死锁的问题
        Long expired = redisUtils.stringRedisTemplate.getExpire(key);
        //若未超时, 则抢锁失败
        if (expired == null || expired != -1) {
            return false;
        }
        lockResult = redisUtils.stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.MILLISECONDS);
        return lockResult != null && lockResult;
    }

    /**
     * 释放分布式锁
     *
     * @param key   键
     * @param value 值
     */
    public static void release(String key, String value) {
        //使用lua脚本, 保证操作的原子性
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptText(luaScript);
        redisUtils.stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);
    }

    /**
     * 防刷设置
     *
     * @param key         设置过期时间key  key的格式 Constant.*PREFIX 如：photo_limit_zhangsan 禁用key格式 Constant.*PREFIX + DISABLE   photo_limit_disable_zhangsan
     * @param time        设置过期时间
     * @param count
     * @param disableTime 禁止访问时间设置， 默认设置30分钟
     * @return 返回boolean sonar检测不过改为返回string
     */
    public static String requestLimit(String key, String disableKey, Long time, Integer count, Long disableTime) {
        /**
         * 禁用key已存在
         */
        Object object = redisUtils.redisTemplate.opsForValue().get(disableKey);
        if (!Objects.isNull(object)) {
            return disableKey;
        }
        Integer limit = (Integer) redisUtils.redisTemplate.opsForValue().get(key);
        if (Objects.isNull(limit)) {
            redisUtils.redisTemplate.opsForValue().set(key, 1, Duration.ofSeconds(time));
            return key;
        }
        /**
         * 超过指定次数则设置该key指定时间才能再次访问,同时要删除原始key
         */
        if (limit > count) {
            redisUtils.redisTemplate.opsForValue().set(disableKey, 1, Objects.isNull(disableTime) ? Duration.ofSeconds(300) : Duration.ofSeconds(disableTime));
            removeKey(key);
            return disableKey;
        } else {
            redisUtils.redisTemplate.opsForValue().increment(key);
            return key;
        }
    }

    /**
     * 删除key
     *
     * @param key
     */
    public static void removeKey(String key) {
        redisUtils.redisTemplate.delete(key);
    }


}
